package store.aixx.security.ch3.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.zalando.problem.spring.web.advice.security.SecurityProblemSupport;
import store.aixx.security.ch3.filter.RestAuthticationFilter;

import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
 * @author yukai
 * @since 2021年11月29日 21:37
 */
@Slf4j
@Configuration
@EnableWebSecurity(debug = true)
@Import(SecurityProblemSupport.class)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ObjectMapper objectMapper;

    private final SecurityProblemSupport securityProblemSupport;

    public SecurityConfig(ObjectMapper objectMapper, SecurityProblemSupport securityProblemSupport){
        this.objectMapper = objectMapper;
        this.securityProblemSupport = securityProblemSupport;
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .inMemoryAuthentication()
                .withUser("zhangsan")
                .password(passwordEncoder().encode("12345678"))
                .roles("USER", "ADMIN");
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // http
        //         .authorizeRequests()
        //         .anyRequest().authenticated()
        //         .and()
        //         .formLogin()
        //         .loginPage("/login")
        //         .usernameParameter("username")
        //         .and()
        //         .httpBasic()
        //         .realmName("BA")
        // ;
        // http.authorizeRequests(req -> req.anyRequest().authenticated())
        //         .formLogin(
        //                 AbstractAuthenticationFilterConfigurer::permitAll
        //         )
        //         .httpBasic(httpBasic -> httpBasic.realmName("BA"))
        //         .csrf(AbstractHttpConfigurer::disable);
        http
                .exceptionHandling(
                        exp  -> exp.accessDeniedHandler(securityProblemSupport)
                                .authenticationEntryPoint(securityProblemSupport)
                )
                .authorizeRequests(req ->
                        req
                                .antMatchers("/authorize/**", "/error").permitAll()
                                .antMatchers("/admin/**").hasRole("ADMIN")
                                .antMatchers("/api/**").hasRole("USER")
                                .anyRequest().authenticated()
                )
                // 把 Filter 加入过滤器链中替代
                .addFilterAt(restAuthticationFilter(), UsernamePasswordAuthenticationFilter.class)
                // .formLogin(
                //         form -> form.loginPage("/login").permitAll()
                //                 // 登录成功的处理
                //                 .successHandler(jsonAuthenticationSuccessHandler())
                //                 .failureHandler(jsonFailureHandler())
                // )
                .csrf(AbstractHttpConfigurer::disable)
                // .httpBasic(Customizer.withDefaults())
        ;

    }

    /**
     * 登录失败处理
     * @return
     */
    private AuthenticationFailureHandler jsonFailureHandler() {
        return (req, res, ex) -> {
            res.setStatus(HttpStatus.UNAUTHORIZED.value());
            res.setContentType(MediaType.APPLICATION_JSON_VALUE);
            res.setCharacterEncoding(StandardCharsets.UTF_8.name());
            Map<String, Object> result = new HashMap<>();
            result.put("titile", "认证失败");
            result.put("details", ex.getMessage());
            res.getWriter().println(objectMapper.writeValueAsString(result));
        };
    }

    /**
     * 自定义过滤器
     * @return 自定义过滤器对象
     */
    private RestAuthticationFilter restAuthticationFilter() throws Exception {
        RestAuthticationFilter restAuthticationFilter = new RestAuthticationFilter(objectMapper);
        // 设置认证成功处理器
        restAuthticationFilter.setAuthenticationSuccessHandler(jsonAuthenticationSuccessHandler());
        // 设置认证失败处理器
        restAuthticationFilter.setAuthenticationFailureHandler(jsonFailureHandler());
        //设置身份认证管理器
        restAuthticationFilter.setAuthenticationManager(authenticationManager());
        // Filter作用的地址
        restAuthticationFilter.setFilterProcessesUrl("/authorize/login");
        return restAuthticationFilter;
    }

    // 登录成功的处理
    private AuthenticationSuccessHandler jsonAuthenticationSuccessHandler() {
        return (req, res, auth) -> {
            res.setStatus(HttpStatus.OK.value());
            res.getWriter().println(objectMapper.writeValueAsString(auth));
            log.debug("认证成功");
        };
    }

    /**
     * 不启用过滤器链，允许或拒绝
     * 例如：
     *      1. 过滤静态资源请求
     *
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                // 把常见类库资源不经过过滤器链，比如css等
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }


}
